package com.pocket.insight.base.sql;

import com.pocket.insight.base.context.RelationToSqlContext;
import com.pocket.insight.base.mql.*;
import com.pocket.insight.base.sql.dialect.MongoDialect;
import com.pocket.insight.base.sql.exprEval.MongoEval;
import com.pocket.insight.entity.AppParam;
import com.pocket.insight.entity.ME;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 描述：
 *
 * @author shuize
 * @create 2024/12/6
 */
@Data
public class SqlEval extends BaseEval {

    protected static final Logger                logger = LoggerFactory.getLogger(SqlEval.class);
    public                 SchemaProvider        schemaProvider;
    protected              EvalMode              evalMode;
    protected              Map<String, TypedVal> refMap;
    public                 TypedVal              tableQuery;
    protected              String                tableAlias;
    private                Timestamp             nowTimestamp;
    private   Map<ME, TypedVal> exprValCache;
    protected Conversion        conversion;
    protected MQL mql = new MQL();
    protected TypedVal.EvalScope evalScope = TypedVal.EvalScope.constant;

    public SqlEval(Dialect dialect) {
        this.evalScope = TypedVal.EvalScope.constant;
        this.evalMode = SqlEval.EvalMode.EVAL_MODE_AGGREGATE;
        this.refMap = new HashMap();
        this.tableQuery = null;
        this.tableAlias = null;
        this.exprValCache = new HashMap();
        this.setDialect(dialect);
        this.mql = new MQL();
        if (RelationToSqlContext.getNowTimestamp() != null) {
            this.nowTimestamp = RelationToSqlContext.getNowTimestamp();
        } else {
            this.nowTimestamp = new Timestamp(System.currentTimeMillis());
        }
    }

    public SqlEval(Relation relation) {
        this.evalScope = TypedVal.EvalScope.constant;
        this.evalMode = SqlEval.EvalMode.EVAL_MODE_AGGREGATE;
        this.refMap = new HashMap();
        this.tableQuery = null;
        this.tableAlias = null;
        this.exprValCache = new HashMap();
        if (relation != null) {
            this.schemaProvider = relation.getSchemaProvider();
            this.setDialect(relation.getDialect());
            this.tableAlias = relation.getAlias();
            if (RelationToSqlContext.getNowTimestamp() != null) {
                this.nowTimestamp = RelationToSqlContext.getNowTimestamp();
            } else {
                this.nowTimestamp = new Timestamp(System.currentTimeMillis());
            }
        } else {
            this.nowTimestamp = new Timestamp(System.currentTimeMillis());
        }
        this.mql = new MQL();
    }

    public void setDialect(Dialect dialect) {
        this.dialect = dialect;
        this.conversion = Conversion.createConversion(dialect);
    }

    public void putRef(String var, TypedVal typedVal) {
        this.refMap.put(var, typedVal);
    }

    public static String idToAlias(Long id) {
        return "dataset_" + id;
    }

    public TypedVal.EvalScope getScope() {
        return this.evalScope;
    }

    public List<String> getRefUids() {
        return this.refMap.keySet().stream().collect(Collectors.toList());
    }

    public static SqlEval createEval(Relation relation) {
        return (relation != null && relation.getDialect() instanceof MongoDialect ? new MongoEval(relation) : new SqlEval(relation));
    }

    public static SqlEval createEval(Dialect dialect) {
        return new SqlEval(dialect);
    }

    @Override
    public TypedVal evaluate(ME expr) {
        ME me = Rewriter.applyAllRules(expr, this.dialect, this.nowTimestamp, new AppParam.ValueMap(null), this);
        return super.evaluate(me);
    }

    public SqlEval prepare(EvalMode evalMode) {
        this.evalMode = evalMode;
        this.evalScope = TypedVal.EvalScope.constant;
        return this;
    }

    public enum EvalMode {
        EVAL_MODE_DATASET,
        EVAL_MODE_AGGREGATE;
    }

    public enum EvalScope {
        constant,
        row,
        distinct,
        aggregate,
        window;
    }
}
